﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLite.Data;
using System.IO;
using System.Globalization;
using NLite.Data.Dialect;
using NLite.Threading;
using NLite.Text;
using System.Data.Common;
using System.Diagnostics;
using System.Data;
using NLite.Data.SqlTypes;
using NLite.Data.Internal;
using NLite.Reflection;
using NLite.Collections;

namespace NLite.Data
{
   

    public class NodeWriter : NodeVisitor
    {
        private SqlBuilder builder;
        public NodeWriter()
        {
            builder = SqlBuilder.Create();
            Local.Data["DbExpressionVisitor"] = this;
        }

        public SqlBuilder Builder
        {
            get
            {
                if (builder == null)
                {
                    builder = SqlBuilder.Create();
                }
                return builder;
            }
            set
            {
                if (value == null)
                    value = SqlBuilder.Create();

                if (builder != null)
                    builder.Clear();

                builder = value;
            }
        }

        #region Log


        public string Sql
        {
            get
            {
                var txt = Builder.ToString();
                return txt;
            }
        }

        public override string ToString()
        {
            return Sql;
        }
        #endregion

        //TODO:will be remove
        public SqlBuilder Reset(string sql)
        {
            var old = SqlBuilder.Create(Builder.Parameters,new StringBuilder( Builder.Buffer.ToString()));

            Builder.Buffer.Length = 0;
            Builder.Buffer.Append(sql);

            return old;
        }

        public SqlBuilder Reset()
        {
            var old = SqlBuilder.Create(Builder.Parameters, new StringBuilder(Builder.Buffer.ToString()));

            Builder.Clear();

            return old;
        }

        public SqlBuilder Reset(SqlBuilder ctx)
        {
            if (ctx == null)
                throw new ArgumentNullException("ctx");

            var old = SqlBuilder.Create(Builder.Parameters, new StringBuilder(Builder.Buffer.ToString()));
            Builder = ctx;
            return old;
        }
        public Dialect.Dialect Dialect { get { return DbConfig.Dialect; } }


        #region Help methods


        protected static bool IsExpressionSubQueryOrFunction(string expression)
        {
            if (String.IsNullOrEmpty(expression))
                return false;

            expression = expression.ToUpper(CultureInfo.InvariantCulture);
            if (expression.StartsWith("SELECT "))
                return true;
            else if (expression.StartsWith("CASE "))
                return true;
            else
                return false;
        }

        public void VisitEnumerable(INode[] items, string separetor)
        {
            var length = items.Length;
            for (int i = 0; i < length; i++)
            {
                items[i].Accept(this);
                if (i < length - 1)
                    Builder.Append(separetor);
            }
        }

        public void VisitEnumerable<T>(T[] items, Action<T> handler, string separetor)
        {
            var length = items.Length;
            for (int i = 0; i < length; i++)
            {
                handler(items[i]);
                if (i < length - 1)
                    Builder.Append(separetor);
            }
        }

        public void VisitEnumerable(INode[] items)
        {
            const string separator = ", ";
            VisitEnumerable(items, separator);
        }

        protected string FormatColumnOrExpression(string column)
        {
            string resultExpression = column.Trim();

            if (resultExpression.StartsWith("("))
                return resultExpression;

            if (IsExpressionSubQueryOrFunction(resultExpression))
                return "(" + resultExpression + ")";
            else
                return DbConfig.Dialect.Quote(resultExpression);
        }


        #endregion

        
      

        protected override SelectNode VisitSelect(SelectNode select)
        {
            select = ColumnRewriter.Rewrite(select);
            var tmpSelect = select.Clone() as SelectNode;
            tmpSelect.Collections.Clear();

            Dialect.BuildSelect(this, tmpSelect);

            if (select.Collections.Count > 0)
            {
                if(select.Collections.Any(p=>!DbConfig.Dialect.SupportsCollectionOperation(p.Type)))
                    throw new NotSupportedException();

                foreach (var item in select.Collections)
                {
                    Builder.AppendLine();
                    Builder.Append(Dialect.FormatCollectionOperation(item.Type)).Append("(");
                    Builder.AppendLine(Indentation.Inner);

                    var rs = item.Query.Result;

                    Builder.Append(Builder.MergeParameters(rs));
                    Builder.AppendLine(Indentation.Outer);
                    Builder.Append(")");

                }
            }
            return select;
        }

       

        protected override SelectIntoNode VisitSelectInto(SelectIntoNode exp)
        {
            Builder.Append(" INTO ");
            Builder.Append(exp.Table).Append(" ");
            if (!string.IsNullOrEmpty(exp.AnotherDatabase))
                Builder.Append(" IN ").Append(Dialect.Quote(exp.AnotherDatabase));
            return exp;
        }

        protected override CaseNode VisitCase(CaseNode exp)
        {
            Builder.Append("(");
            exp.ToFunction().Accept(this);
            Builder.Append(")");
            return exp;
        }

        protected override IFunctionNode VisitFunction(IFunctionNode exp)
        {
            if (exp.Args != null && exp.Args.Length > 0)
            {
                if (exp.Args.Length >= 1)
                {
                    var col = exp.Args[0] as IColumnNode;
                    if(col != null) col.Expression = null;//解除双向依赖
                }
            }

            exp.Function.Render(this, exp.Args);

            return exp;
        }

        protected override SqlObject VisitSqlObject(SqlObject exp)
        {
            if (!object.ReferenceEquals(null, exp)
                && !object.Equals(null, exp.Expression))
                Visit(exp.Expression);

            return exp;
        }

        protected override AliasNode VisitAlias(AliasNode exp)
        {
            Visit(exp.Projection);

            if (exp.Alias != null)
            {
                Builder.Append(" AS ");
                Builder.Append(DbConfig.Dialect.Quote(exp.Alias));
            }

            return exp;
        }

        protected override BetweenNode VisitBetween(BetweenNode between)
        {
            Visit(between.Column);
            Builder.Append(" BETWEEN ").Append(() => Visit(between.Left)).Append(" AND ").Append(() => Visit(between.Right));
            return between;
        }

        protected override LikeNode VisitLike(LikeNode like)
        {
            Visit(like.Column);
            Builder.Append(" LIKE ").Append(() => Visit(like.Value));
            return like;
        }

        protected override SubQuery VisitSubQuery(SubQuery subQuery)
        {
            if (!DbConfig.Driver.SupportsSubQuery)
                throw new NotSupportedException();

            Visit(subQuery.Column);
            Builder.Append(" ").Append(subQuery.Type.GetDescription()).Append("(");
            if (subQuery.Query != null)
            {
                Builder.AppendLine(Indentation.Inner);
                VisitSelect(subQuery.Query);
                Builder.AppendLine(Indentation.Outer);
            }
            else if (subQuery.Range != null)
                VisitEnumerable(subQuery.Range.ToArray());
            Builder.Append(")");

            return subQuery;
        }

        protected override ArithmeticNode VisitArithmetic(ArithmeticNode arithmetic)
        {
        	var needWrapper = arithmetic.Priority;
            if (needWrapper.HasValue && needWrapper.Value)
                Builder.Append("(").Append(() => Visit(arithmetic.Left)).Append(")");
            else
                Builder.Append(() => Visit(arithmetic.Left));
        	
        	Builder.Append(" ").Append(arithmetic.Operator.GetDescription()).Append(" ")
        		.Append(()=>Visit(arithmetic.Right));

            return arithmetic;
        }

        protected override CountNode VisitCount(CountNode exp)
        {
            string columnName = "*";
            if(!object.Equals(exp.Column,null))
            {
                exp.Column.Expression = null;//解除双向依赖
                var col = exp.Column.Clone() as ColumnNode;
                col.Expression = exp.Criterion;

                columnName = col.Result.Text;
            }

            Builder.Append("COUNT")
                     .Append("(")
                     .Append(columnName)
                     .Append(")");

            return exp;
        }

        protected override AggregationNode VisitAggregation(AggregationNode exp)
        {
            exp.Node.Expression = null;//解除双向依赖

            var col = exp.Node.Clone() as IColumnNode;
            col.Expression = exp.Expression;
            Builder.Append(exp.Operator.GetDescription())
                .Append("(")
                .Append(() => Visit(col))
                .Append(")");

            return exp;
        }

        protected override IsNullNode VisitIsNull(IsNullNode exp)
        {
            Visit(exp.Value);
            Builder.Append(" IS NULL");

            return exp;
        }

        protected override IsNullOrEmptyNode VisitIsNullOrEmpty(IsNullOrEmptyNode exp)
        {
            Builder.Append("(");
            Visit(exp.Value);
            Builder.Append(" IS NULL OR ");
            Visit(exp.Value);
            Builder.Append(" = '')");

            return exp;
        }

        protected override IParameterNode VisitParameter(IParameterNode exp)
        {
            var o = exp as SqlObject;
            var index = Builder.ParameterCount;
            var parameterName = "p" + index.ToString();

            if (!DbConfig.SupportsParameterInSql)
            {
                if (o != null)
                    Builder.Append(Dialect.ToSqlString(o.Type.DbType, o.Value));
                else
                    Builder.Append(Dialect.ToSqlString(null, o.Value));
                return exp;
            }

            var dp = exp as DataParameter;
            var p = o != null
                ? Builder.AddParameter(o.Identity, parameterName, exp.Value)
                : dp != null ?
                        Builder.AddParameter(dp.Identity, parameterName, exp.Value)
                        :Builder.AddParameter(parameterName, exp.Value);

            if(DbConfig.Driver.UseNamedPrefixInSql)
                Builder.Append(DbConfig.Driver.NamedPrefix).Append(parameterName);
            else
                Builder.Append(DbConfig.Driver.NamedPrefix).Append(Dialect.ToSqlString(p));
            return exp;
        }

        protected override IColumnNode VisitColumn(IColumnNode column)
        {
            if (column == null)
                throw new ArgumentNullException("column");
            if (string.IsNullOrEmpty(column.Identity.Name))
                throw new ArgumentException("Column name cannot be empty", "ColumnNameOrExpression");

            if (column.Expression != null)
                Visit(column.Expression);
            else
            {
                if (!string.IsNullOrEmpty(column.Table))
                {
                    Builder.Append(DbConfig.Dialect.Quote(column.Table))
                    .Append(".");
                }

                Builder.Append(FormatColumnOrExpression(column.Identity.Name));
            }

            return column;
        }

        protected override InsertNode VisitInsert(InsertNode insertExp)
        {
            var tmp = ColumnRewriter.Rewrite(insertExp);

            var table = new TableNode(tmp.Name, null);
            var columns = new List<IColumnNode>();
            var values = new List<IParameterNode>();
            foreach (var item in tmp.NameValues)
            {
                columns.Add(item.Name);
                var col = item.Name as SqlObject;
                if (col != null)
                    values.Add(new DataParameter(col.Identity, "p", item.Value));
                else
                    values.Add(new DataParameter("p", item.Value));
            }

            Builder.Append("INSERT INTO ")
                .AppendLine()
                .Append(() => VisitTable(table))
                .AppendLine()
                .Append("(")
                .AppendLine(Indentation.Inner)
                .Append(() => VisitEnumerable(columns.ToArray()))
                .AppendLine(Indentation.Outer)
                .Append(")")
                .AppendLine()
                .Append("VALUES")
                .AppendLine()
                .Append("(")
                .AppendLine(Indentation.Inner)
                .Append(() => VisitEnumerable(values.ToArray()))
                .AppendLine(Indentation.Outer)
                .Append(")");

            return tmp;
        }

        protected override TableNode VisitTable(TableNode table)
        {
            if (String.IsNullOrEmpty(table.Name))
                throw new ArgumentNullException("Table cannot be null", "TableName");

            Builder.Append(DbConfig.Dialect.Quote(table.Name));
            if (!String.IsNullOrEmpty(table.Alias))
                Builder.Append(" AS ").Append(table.Alias);

            return table;
        }

        protected override JoinNode VisitJoin(JoinNode join)
        {
            if (join == null)
                throw new ArgumentNullException("join");
            if (string.IsNullOrEmpty(join.Name))
                throw new ArgumentException("Join table source not set", "TableSource");
            if (join.Condition == null)
                throw new ArgumentNullException("Join condtion");

            var sourceTable = new TableNode(join.Name, join.Alias);

            Builder.Append(join.Type.GetDescription())
                .Append(" ").Append(() => sourceTable.Accept(this))
                .Append(" ON ").Append(() => join.Condition.Accept(this));

            return join;
        }

        protected override WhereNode VisitWhere(WhereNode where)
        {
            if (where == null
                || where.Criterion == null
                || where.Criterion.IsEmpty)
                return where;

            Builder.AppendLine()
                .Append("WHERE ").Append(() => VisitLogic(where.Criterion));

            return where;
        }

        protected override CompareNode VisitCompare(CompareNode binary)
        {
            if (binary == null)
                throw new ArgumentNullException("binary");

            Visit(binary.Left);
            Builder.Append(" ").Append(binary.Operator.GetDescription()).Append(" ");
            Visit(binary.Right);

            return binary;
        }

        protected override LogicNode VisitLogic(LogicNode exp)
        {
            if (exp == null || exp.IsEmpty)
                return exp;

            if (exp.Left != null)
            {
                if (exp.Left is LogicNode)
                {
                    var logic = exp.Left as LogicNode;
                    if (logic.Left != null)
                        Builder.Append("(").Append(() => Visit(exp.Left)).Append(")");
                    else
                        Visit(exp.Left);
                }
                else

                    Visit(exp.Left);

                Builder.Append(" ").Append(exp.Operator.GetDescription()).Append(" ");
            }
            if (exp.Right != null)
            {
                if (exp.Right is LogicNode)
                    Builder.Append("(").Append(() => Visit(exp.Right)).Append(")");
                else
                    Visit(exp.Right);
            }

            return exp;
        }

        protected override NotNode VisitNot(NotNode not)
        {
            Builder.Append("NOT ").Append(() => Visit(not.Expression));
            return not;
        }

        protected override ExistsNode VisitExists(ExistsNode exists)
        {
            Builder.Append("EXISTS")
                .Append("(")
                .Append(() => Visit(exists.Query))
                .Append(")");
            return exists;
        }

        protected override NativeNode VisitNative(NativeNode exp)
        {
            Builder.Append(exp.Expression);
            return exp;
        }

        protected override GroupNode VisitGroup(GroupNode groupBy)
        {
            if (groupBy.Items.Count < 1)
                return groupBy;

            var fields = groupBy.Items.Select(p => new ColumnNode(p.Identity.Name, p.Table));
            Builder.AppendLine().Append("GROUP BY ").Append(() => VisitEnumerable(fields.ToArray()));
            return groupBy;
        }

        protected override HavingNode VisitHaving(HavingNode having)
        {

            if (having == null
                || having.Criterion == null
                || having.Criterion.IsEmpty)
                return having;
            Builder.AppendLine().Append("HAVING ").Append(() => VisitLogic(having.Criterion));
            return having;
        }

        protected override OrderNode VisitOrder(OrderNode orderBy)
        {
            if (orderBy.Items.Count < 1)
                return orderBy;

            Builder.AppendLine()
                .Append("ORDER BY ")
                .Append(() => VisitEnumerable(orderBy.Items.ToArray()));

            return orderBy;
        }

        protected override OrderItemNode VisitOrderItem(OrderItemNode orderItem)
        {
            orderItem.Column.Accept(this);
            if(orderItem.Direction == OrderDirection.Desc)
                Builder.Append(" ").Append(orderItem.Direction.ToString().ToUpper());

            return orderItem;
        }

        protected override DeleteNode VisitDelete(DeleteNode delete)
        {
            if (delete == null)
                throw new ArgumentNullException("delete");

            if (string.IsNullOrEmpty(delete.Name))
                throw new ArgumentException("Table name cannot be empty", "TableName");

            var tmp = ColumnRewriter.Rewrite(delete);

            var table = new TableNode(tmp.Name, tmp.Alias);

            Builder.Append("DELETE FROM ").Append(() => table.Accept(this))
                .Append(() => VisitWhere(tmp.Where));

            return tmp;
        }

        protected override UpdateNode VisitUpdate(UpdateNode update)
        {
            if (update == null)
                throw new ArgumentNullException("update");

            if (string.IsNullOrEmpty(update.Name))
                throw new ArgumentException("Table name cannot be empty", "TableName");

            var table = new TableNode(update.Name, update.Alias);

            Builder.Append("UPDATE ").Append(() => table.Accept(this))
                .Append(" SET ")
                .Append(() => VisitEnumerable<NameValue>(update.NameValues.ToArray()
                    , pair => 
                        {
                            VisitColumn(pair.Name);
                            Builder.Append(" = ");
                            VisitParameter((IParameterNode)Builder.AddParameter(pair.Value));
                        }
                    , ", "))
                .Append(() => update.Where.Accept(this));
            return update;
        }
    }
}
